<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>Polar Clock</title>
        <meta name="viewport" content="width=device-width">
        <script src="raphael.js"></script>
        <script>



//http://raphaeljs.com/reference.html
//http://google-styleguide.googlecode.com/svn/trunk/javascriptguide.xml
//https://developers.google.com/closure/compiler/docs/js-for-compiler
//https://developer.mozilla.org/en-US/docs



var PolarClock = { instance: null, paper: null };



PolarClock.Options = {
    Display: {Stacked: 0, Seperated: 1},
    StackedDimensions: {Radius: 100, RingWidth: 30, Width: 410},
    SeperatedDimensions: {Radius: 40, Spacing: 10, RingWidth: 30, Width: 540},
    getDaysInMonth: function(time) {
        time.setDate(1);
        time.setMonth(time.getMonth() + 1);
        time.setDate(0);
        return time.getDate();
    },
    Interval: {
        Minutes: { index: 4, getAngle:
            function(t) {
                var i = 60;  //# of intervals for this time unit
                var mspi = 1000 * 60;  //milliseconds per interval for this time unit
                var currentTime = t.getTime();  //current time value in unix epoch
                var currentTimeUnit = t.getMinutes();  //current time interval value
                t.setMilliseconds(0);
                t.setSeconds(0);
                return (currentTimeUnit * mspi + (currentTime - t.getTime())) * 360 / (i * mspi);
            }
        },
        Seconds: { index: 5, getAngle:
            function(t) {
                var i = 60;  //# of intervals for this time unit
                var mspi = 1000;  //milliseconds per interval for this time unit
                var currentTime = t.getTime();  //current time value in unix epoch
                var currentTimeUnit = t.getSeconds();  //current time interval value
                t.setMilliseconds(0);
                return (currentTimeUnit * mspi + (currentTime - t.getTime())) * 360 / (i * mspi);
            }
        },
        Hours: { index: 3, getAngle:
            function(t) {
                var i = 24;  //# of intervals for this time unit
                var mspi = 1000 * 60 * 60;  //milliseconds per interval for this time unit
                var currentTime = t.getTime();  //current time value in unix epoch
                var currentTimeUnit = t.getHours();  //current time interval value
                t.setMilliseconds(0);
                t.setSeconds(0);
                t.setMinutes(0);
                return (currentTimeUnit * mspi + (currentTime - t.getTime())) * 360 / (i * mspi);
            }
        },
        Months: { index: 1, getAngle:
            function(t) {
                var i = 12;  //# of intervals for this time unit
                var mspi =  1000 * 60 * 60 * 24 * PolarClock.Options.getDaysInMonth(new Date(t));  //milliseconds per interval for this time unit
                var currentTime = t.getTime();  //current time value in unix epoch
                var currentTimeUnit = t.getMonth();  //current time interval value
                t.setMilliseconds(0);
                t.setSeconds(0);
                t.setMinutes(0);
                t.setHours(0);
                t.setDate(1);
                return (currentTimeUnit * mspi + (currentTime - t.getTime())) * 360 / (i * mspi);
            }
        },
        Days: { index: 2, getAngle:
            function(t) {
                var i = PolarClock.Options.getDaysInMonth(new Date(t));  //# of intervals for this time unit
                var mspi = 1000 * 60 * 60 * 24;  //milliseconds per interval for this time unit
                var currentTime = t.getTime();  //current time value in unix epoch
                var currentTimeUnit = t.getDate();  //current time interval value
                t.setMilliseconds(0);
                t.setSeconds(0);
                t.setMinutes(0);
                t.setHours(0);
                return ((currentTimeUnit - 1) * mspi + (currentTime - t.getTime())) * 360 / (i * mspi);
            }
        },
        DaysOfWeek: { index: 0, getAngle:
            function(t) {
                var i = 7;  //# of intervals for this time unit
                var mspi = 1000 * 60 * 60 * 24 * 7;  //milliseconds per interval for this time unit
                var currentTime = t.getTime();  //current time value in unix epoch
                var currentTimeUnit = t.getDay();  //current time interval value
                t.setMilliseconds(0);
                t.setSeconds(0);
                t.setMinutes(0);
                t.setHours(0);
                t.setDate(t.getDate() - t.getDay());
                return (currentTimeUnit * mspi + (currentTime - t.getTime())) * 360 / (i * mspi);
            }
        }
    }
}



PolarClock.Ring = function(canvas, intervalInfo, config) {
    var self = this;
    self.canvas = canvas;
    self.angleMethod = intervalInfo.getAngle;
    self.center = config.Sizes.Center;
    self.scale = config.Sizes.Scale;
    self.index = intervalInfo.index;
    self.angleDeg = -1;
    self.shadow = self.canvas.clone();
    self.canvas.toFront();
    self.display = config.Display;

    /*
     * Set the width and radius of the ring.  Initialize the degree to -1 so it will redraw on resize.
     */
    var setSizes = function() {
        self.angleDeg = -1;
        var stackedDims = PolarClock.Options.StackedDimensions;
        var seperatedDims = PolarClock.Options.SeperatedDimensions;

        switch (self.display) {
            case PolarClock.Options.Display.Seperated:
                self.canvas.attr({"stroke-width": seperatedDims.RingWidth * self.scale.Seperated});
                self.radius = (seperatedDims.Radius + (seperatedDims.Spacing + seperatedDims.RingWidth) * self.index) * self.scale.Seperated;
                self.shadow.hide();
                break;
            case PolarClock.Options.Display.Stacked:
                self.canvas.attr({"stroke-width": stackedDims.RingWidth * (6 - self.index) * self.scale.Stacked});
                self.radius = stackedDims.Radius * self.scale.Stacked;
                self.shadow.show();
                self.shadow.attr({stroke: "#111", "stroke-width": (stackedDims.RingWidth * (6 - self.index) + 2) * self.scale.Stacked, opacity:.3});
                break;
        }
    }

    setSizes();

    self.update = function(time) {
        var temp;

        if ((temp = self.angleMethod(time)) > self.angleDeg + .1 || temp < self.angleDeg) {  //dont update for minor changes (perf improv)
            self.angleDeg = temp;
            var angleRad = self.angleDeg * Math.PI / 180;
            var largeArcFlag = (self.angleDeg > 180 ? 1 : 0);
            var newPath = "m "+ self.center.x +" " + (self.center.y - self.radius) + " A "+ self.radius +" "+self.radius+" 1 "+ largeArcFlag+" 1 " + (self.center.x + self.radius * Math.sin(angleRad)) + " " + (self.center.y - self.radius * Math.cos(angleRad));
            self.canvas.attr({path: newPath, stroke: "hsl(" + (self.angleDeg / 360) + ", 1, .5)"});

            self.shadow.attr({path: newPath});
        }
    }

    /*
     * Called to resize the ring based on the new center and scale.
     *
     * @param {{x: number, y: number}} center
     * @param {number} scale
     */
    self.resize = function(center, scale) {
        self.scale = scale;
        self.center = center;

        setSizes();
    }

    self.setDisplay = function(display) {
        self.display = display;
        setSizes();
    }
}



PolarClock.Clock = function() {
    var self = this;
    var Ring = PolarClock.Ring;
    var Opts = PolarClock.Options;
    this.display = Opts.Display.Seperated;

    /*
     * @return {{ Scale: number, Center: {{x: number, y: number}}, Width: number, Height: number }}
     */
    var getSizeInfo = function() {
        var minDim = document.documentElement.clientWidth < document.documentElement.clientHeight ? document.documentElement.clientWidth : document.documentElement.clientHeight;
        var height = document.documentElement.clientHeight - 5;  //hack: magic 4px removes scroll bars
        var width = document.documentElement.clientWidth;
        var center = {x: width / 2, y: height / 2};

        var stackedScale = minDim / Opts.StackedDimensions.Width;
        var seperatedScale = minDim / Opts.SeperatedDimensions.Width;

        return {Scale : {Stacked: stackedScale, Seperated: seperatedScale}, Center : center, Width : width, Height: height};
    }

    var sizes = getSizeInfo();

    PolarClock.paper = Raphael("clock", sizes.Width, sizes.Height);
    var paper = PolarClock.paper;

    var config = {Sizes: sizes, Display: this.display};

    var rings = [
        new Ring(paper.path(), Opts.Interval.DaysOfWeek, config),
        new Ring(paper.path(), Opts.Interval.Months, config),
        new Ring(paper.path(), Opts.Interval.Days, config),
        new Ring(paper.path(), Opts.Interval.Hours, config),
        new Ring(paper.path(), Opts.Interval.Minutes, config),
        new Ring(paper.path(), Opts.Interval.Seconds, config)
    ];

    this.update = function(time) {
        for (var i = 0; i < rings.length; ++i) {
            rings[i].update(new Date(time.getTime()));
        }
    }

    this.resize = function() {
        var sizes = getSizeInfo();

        paper.setSize(sizes.Width, sizes.Height);

        for (var i = 0; i < rings.length; ++i) {
            rings[i].resize(sizes.Center, sizes.Scale);
        }
    }

    this.toggleDisplay = function() {
        var stacked = PolarClock.Options.Display.Stacked;
        var seperated = PolarClock.Options.Display.Seperated;

        this.display = (this.display === stacked ? seperated : stacked);

        for (var i = 0; i < rings.length; ++i) {
            rings[i].setDisplay(this.display);
        }
    }

    function tick() {
        self.update(new Date());

        if (typeof requestAnimationFrame === "undefined") {
            self.intervalId = setTimeout(tick, 16);
        } else {
            requestAnimationFrame(tick);
        }
    }

    tick();
}



window.onload = function() {
    document.getElementsByTagName('body')[0].onclick = function() {PolarClock.instance.toggleDisplay();}
    PolarClock.instance = new PolarClock.Clock();
}

window.onresize = function() {
    PolarClock.instance.resize();
}



        </script>
        <style>
            body {background:#000;margin:0;/*height:1280px;width:1024px;*/}
            #clock{height:100%;width:100%;}
        </style>
    </head>
    <body>
        <div id="clock"></div>
    </body>
</html>
